<script setup lang="ts">
import { computed, onMounted, reactive, useSlots, watch } from 'vue'
import type { UploadCustomRequestOptions } from 'naive-ui'
import { randomCode } from '@/utils'
import { JBoltApi } from '@/utils/request'
import { UploadTo } from '@/typings/common'
import type { FileInfo } from 'naive-ui/es/upload/src/interface'
import { getServiceEnvConfig } from '~/.env-config'

const slots = useSlots()
const props = withDefaults(
    defineProps<{
        /** 是否只用于展示文件列表，不上传 */
        onlyShow?: boolean
        /**上传到哪里，暂时只支持七牛 */
        uploadTo?: UploadTo
        /** 使用哪个七牛账号上传，不传则使用默认的，但需要后台配置默认账号 */
        qiniuSn?: string
        /**上传到服务器的路径，支持动态参数，参考 src/utils/common/file.ts中generatePath说明。 */
        savePath?: string
        /** 绑定的数据 */
        modelValue: string[] | string
        /** 上传的文件地址是否join成一个字符串，false代表不join返回数组结构，字符串代表join使用的字符，value返回的是join后的一个完整字符串*/
        valueJoin?: false | string
    }>(),
    {
        valueJoin: ',',
        onlyShow: false
    }
)
const emit = defineEmits<{
    /** 绑定的数据 */
    'update:modelValue': [value: string[] | string]
}>()

const uploadTo = computed(() => {
    const { defaultUploadTo } = getServiceEnvConfig(import.meta.env)
    return props.uploadTo || defaultUploadTo
})
const qiniuSn = computed(() => {
    const { defaultQiniuSn } = getServiceEnvConfig(import.meta.env)
    return props.qiniuSn || defaultQiniuSn
})

const state = reactive<{ fileList: FileInfo[] }>({
    fileList: []
})
watch(
    () => props.modelValue,
    () => {
        if (state.fileList.length == 0) {
            syncFileListByModelValue()
        }
    }
)

async function toUpload({
    file,
    onFinish,
    onError,
    onProgress
}: UploadCustomRequestOptions) {
    let url = null
    if (!file.file) return
    try {
        switch (uploadTo.value) {
            case 'qiniu':
                url = await JBoltApi.uploadToQiniu(
                    file.file,
                    props.savePath,
                    qiniuSn.value ?? '',
                    ({ percent }) => {
                        onProgress({ percent })
                    }
                )
                break
            case 'server':
                url = await JBoltApi.uploadToServer(
                    file.file,
                    props.savePath,
                    ({ percent }) => {
                        onProgress({ percent })
                    }
                )
                break
            default:
                throw new Error('不支持的上传类型')
        }
        file.url = url as string
        onFinish()
        const value = state.fileList
            .filter((item) => item.status == 'finished')
            .map((item) => item.url)
        if (props.valueJoin) {
            emit('update:modelValue', value.join(props.valueJoin))
        } else {
            emit('update:modelValue', value as string[])
        }
    } catch (e) {
        onError()
    }
}

function removeFile({ file }: { file: FileInfo }) {
    const value = state.fileList
        .filter((item) => item.status == 'finished' && item.id != file.id)
        .map((item) => item.url)
    if (props.valueJoin) {
        emit('update:modelValue', value.join(props.valueJoin))
    } else {
        emit('update:modelValue', value as string[])
    }
}

function syncFileListByModelValue() {
    let value = props.modelValue
    if (typeof value == 'string' && props.valueJoin) {
        value = value.split(props.valueJoin as string)
    } else if (typeof value == 'string' && !props.valueJoin) {
        throw new Error('valueJoin为false时，modelValue的值不能是字符串')
    }
    state.fileList = (<string[]>value).map((url) => {
        return {
            id: randomCode(8),
            name: url.substring(url.lastIndexOf('/') + 1),
            status: 'finished',
            url: url
        }
    })
}

onMounted(() => {
    if (props.modelValue) {
        syncFileListByModelValue()
    }
})
</script>

<template>
    <n-upload
        v-model:file-list="state.fileList"
        :custom-request="toUpload"
        :class="{ notupload: props.onlyShow }"
        @remove="removeFile"
    >
        <template v-for="(_item, key) in slots" :key="key" #[key]="data">
            <slot :name="key" v-bind="data ?? {}" />
        </template>
    </n-upload>
</template>

<style scoped>
.notupload :deep(.n-upload-trigger) {
    display: none;
}
</style>
